import java.util.ArrayList;
import java.util.Random;

import static java.lang.Thread.sleep;

/**
 * This class represents an Intersecion object
 * @author Matthew Page
 * Date: 19/08/2010
 * Time: 10:21:09 AM
 */
public class InterGrid {
    private int MINHORLANES = 1;
    private int MAXHORLANES = 3;
    private int MINVERTLANES = 1;
    private int MAXVERTLANES = 4;
    private int MINPROB = 0;
    private int MAXPROB = 1;

    Random rand = new Random();
    ArrayList<Car> cars;
    ArrayList<String> carDump;
    TrafficController trafficCont;
    private char grid[][];
    private char carSymbol = 'C';
    private int vertColStart;
    private int vertRowStart;
    private int horColStart;
    private int horRowStart;

    private int numHorLanes = 1;
    private int numVertLanes = 1;
    private int carLengthsPerLane = 10;
    private int westVertBound = carLengthsPerLane - 1;
    private int eastVertBound = carLengthsPerLane + numVertLanes;
    private int northHorBound = carLengthsPerLane - 1;
    private int southHorBound = carLengthsPerLane + numHorLanes;
    private double horProbability = 0.5;
    private double vertProbability = 0.5;

    private int horRowLight = carLengthsPerLane - 1;
    private int horColLight = carLengthsPerLane - 1;
    private int vertRowLight = carLengthsPerLane + numHorLanes;
    private int vertColLight = carLengthsPerLane + numVertLanes;

    int numHorSquares = numHorLanes + (carLengthsPerLane * 2);
    int numVertSquares = numVertLanes + (carLengthsPerLane * 2);

    public int getNumHorLanes() {
        return numHorLanes;
    }

    public boolean setNumHorLanes(int numHorLanes) {
        if ((numHorLanes >= MINHORLANES) && (numHorLanes <= MAXHORLANES)) {
            this.numHorLanes = numHorLanes;
            return true;
        } else {
            return false;
        }
    }

    public int getNumVertLanes() {
        return numVertLanes;
    }

    /**
     * Set the number of vertical lanes
     *
     * @param numVertLanes is the number of vertical lanes to be set.
     * @return true OR false if numVertLanes value is within constraints.
     */
    public boolean setNumVertLanes(int numVertLanes) {
        if ((numVertLanes >= MINVERTLANES) && (numVertLanes <= MAXVERTLANES)) {
            this.numVertLanes = numVertLanes;
            return true;
        } else {
            return false;
        }
    }

    public double getHorProbability() {
        return horProbability;
    }

    /**
     * Set the probability of car entering horizontal street.
     *
     * @param horProb is the probability between 0 and 1 that a car will enter the horizontal street
     * @return true if horProb is set and between constraints. Else will return false.
     */
    public boolean setHorProbability(double horProb) {
        if ((horProb > MINPROB) && (horProb < MAXPROB)) {
            this.horProbability = horProb;
            return true;
        } else {
            return false;
        }
    }

    public double getVertProbability() {
        return vertProbability;
    }

    /**
     * Set the probability of a car entering vertical street.
     *
     * @param vertProb is the probability between 0 and 1 that a car will enter the horizontal street
     * @return true if vertProb is set and between constraints. Else will return false.
     */
    public boolean setVertProbability(double vertProb) {
        if ((vertProb > MINPROB) && (vertProb < MAXPROB)) {
            this.vertProbability = vertProb;
            return true;
        } else {
            return false;
        }
    }
    /**
     * Set element at Row, Column to the character passed in.
     * @param r = Row, c = Column, ch = Character to be set
     */
    public void setElement(int r, int c, char ch) {
        this.grid[r][c] = ch;
    }
    /**
     * Takes in a Row and Column parameter and returns the corresponding character.
     * @param r = Row and c = Column
     * @return Char that exists at positon [r][c] on grid.
     */
    public char getElement(int r, int c) {
        return grid[r][c];
    }
    /**
     * Print entire grid to screen.
     */
    public void displayGrid() {
        for (int i = 0; i < grid.length; i++) {
            char[] row = grid[i];
            for (int j = 0; j < row.length; j++) {
                char c = row[j];
                System.out.print(c);
            }
            System.out.println("");

        }
        System.out.println("------------------------End Simulation---------------------------");
    }
    /**
     * Initiate Grid from set details.
     */
    public void initiateGrid() {
        //Create new car list, traffic controller
        cars = new ArrayList<Car>();
        trafficCont = new TrafficController();

        //Set intersection information
        this.numHorSquares = numHorLanes + (carLengthsPerLane * 2);
        this.numVertSquares = numVertLanes + (carLengthsPerLane * 2);
        this.westVertBound = carLengthsPerLane - 1;
        this.eastVertBound = carLengthsPerLane + numVertLanes;
        this.northHorBound = carLengthsPerLane - 1;
        this.southHorBound = carLengthsPerLane + numHorLanes;

        this.vertColStart = this.carLengthsPerLane;
        this.vertRowStart = this.numHorSquares - 1;

        this.horColStart = 0;
        this.horRowStart = this.carLengthsPerLane;

        //Set traffic light locations
        this.horRowLight = carLengthsPerLane - 2;
        this.horColLight = carLengthsPerLane - 2;
        this.vertRowLight = carLengthsPerLane + numHorLanes + 1;
        this.vertColLight = carLengthsPerLane + numVertLanes + 1;

        //Create new grid and fill with empty characters
        grid = new char[numHorSquares][numVertSquares];
        for (int r = 0; r < grid.length; r++) {
            char[] row = grid[r];
            for (int c = 0; c < row.length; c++) {
                row[c] = ' ';
            }
        }
    }
    /**
     * Sets lane boundaries and traffic lights in Grid
     */
    public void createLaneBoundaries() {
        for (int i = 0; i < carLengthsPerLane; i++) {
            grid[i][westVertBound] = '|';
            grid[i][eastVertBound] = '|';
            grid[numHorSquares - (i + 1)][westVertBound] = '|';
            grid[numHorSquares - (i + 1)][eastVertBound] = '|';
            grid[northHorBound][numVertSquares - (i + 1)] = '-';
            grid[southHorBound][numVertSquares - (i + 1)] = '-';
            grid[northHorBound][i] = '-';
            grid[southHorBound][i] = '-';

        }
        //Set traffic lights position
        setElement(this.horRowLight, this.horColLight, trafficCont.getHorState());
        setElement(this.vertRowLight, this.vertColLight, trafficCont.getVertState());
    }
    /**
     * Runs simulation x time's
     * @param x = Number of Simulations
     */
    public void runSimulation(int x) throws InterruptedException {
        double prob;
        int cycle = 0;
        while (cycle < x) {

            grid = new char[numHorSquares][numVertSquares];
            for (int r = 0; r < grid.length; r++) {
                char[] row = grid[r];
                for (int c = 0; c < row.length; c++) {
                    row[c] = ' ';
                }
            }
            createLaneBoundaries();
            if (cars.size() > 0) {
                carDump = new ArrayList<String>();
                for (int i = 0; i < cars.size(); i++) {
                    Car car = cars.get(i);
                    switch (car.getDirection()) {
                        case 'N':
                            //If car is past the street light
                            if (car.getRowLoc() <= this.vertRowLight) {
                                if (car.getNextRowLoc() < 0) {

                                } else {
                                    car.move();
                                    setElement(car.getRowLoc(), car.getColLoc(), carSymbol);
                                }
                            } else {
                                //If car is before street light
                                if (this.trafficCont.getVertState() == 'R') {
                                    //If Light is red
                                    if (car.getNextRowLoc() != this.vertRowLight) {
                                        //If space in front is free
                                        if (grid[car.getNextRowLoc()][car.getNextColLoc()] == ' ') {
                                            car.move();
                                        } else {
                                            //If space next to car in front is free
                                            if (grid[car.getNextRowLoc()][car.getNextColLoc() + 1] == ' ') {
                                                car.setRowLoc(car.getNextRowLoc());
                                                car.setColLoc(car.getNextColLoc() + 1);
                                                car.setNextRowLoc(car.getRowLoc() - 1);
                                                car.setNextColLoc(car.getColLoc());
                                            }
                                        }
                                    }

                                } else {
                                    car.move();
                                }
                                setElement(car.getRowLoc(), car.getColLoc(), carSymbol);

                            }
                            break;
                        case 'E':
                            //If car is past the street light
                            if (car.getColLoc() >= this.horColLight) {
                                if (car.getNextColLoc() > this.numHorSquares - 1) {

                                } else {
                                    car.move();
                                    setElement(car.getRowLoc(), car.getColLoc(), carSymbol);
                                }
                            } else {
                                //If car is before street light
                                if (this.trafficCont.getHorState() == 'R') {
                                    //If Light is red
                                    if (car.getNextColLoc() != this.horColLight) {
                                        //If space in front is free
                                        if (grid[car.getNextRowLoc()][car.getNextColLoc()] == ' ') {
                                            car.move();
                                        } else {
                                            //If space next to car in front is free
                                            if (grid[car.getNextRowLoc() + 1][car.getNextColLoc()] == ' ') {
                                                car.setRowLoc(car.getNextRowLoc() + 1);
                                                car.setColLoc(car.getNextColLoc());
                                                car.setNextRowLoc(car.getRowLoc());
                                                car.setNextColLoc(car.getColLoc() + 1);
                                            }
                                        }
                                    }
                                } else {
                                    car.move();
                                }
                                setElement(car.getRowLoc(), car.getColLoc(), carSymbol);
                            }
                            break;
                    }
                }
            }

            prob = rand.nextDouble();
            //Check if car is to be created on the horizontal street.
            if (prob < this.horProbability && grid[this.horRowStart][this.horColStart] == ' ') {
                int r = this.horRowStart;
                int c = this.horColStart;
                char dir = 'E';
                cars.add(new Car(r, c, dir));
                setElement(r, c, carSymbol);
            }
            //Check if car is to be created on the vertical street.
            if (prob < this.vertProbability && grid[this.vertRowStart][this.vertColStart] == ' ') {
                int r = this.vertRowStart;
                int c = this.vertColStart;
                char dir = 'N';
                cars.add(new Car(r, c, dir));
                setElement(r, c, carSymbol);
            }

            trafficCont.incrementCycle();
            cycle++;
            displayGrid();
            sleep(200);

        }
    }

}